﻿using JESAI.Redis.Abstracts;
using JESAI.Redis.Abstracts.Commands;
using JESAI.Redis.Abstracts.Messages;
using JESAI.Redis.Abstracts.RedisClients;
using JESAI.Redis.Commands;
using JESAI.Redis.Consts;
using JESAI.Redis.Datas;
using JESAI.Redis.Enums;
using JESAI.Redis.Event;
using JESAI.Redis.Internal.Status;
using JESAI.Redis.Messages;
using JESAI.Redis.Pools;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace JESAI.Redis.RedisClients
{
    internal class RedisClient : IRedisClient
    {
        /// <summary>
        /// 消息传输
        /// </summary>
        protected static readonly IMessageTransport MessageTransport = new MessageTransport();

        protected Action<IRedisClient> DisposeCallback;

        /// <summary>
        /// 是否授权
        /// </summary>
        protected bool IsAuth { get; set; }

        /// <summary>
        /// 连接字符串
        /// </summary>
        public IConnectionBuilder ConnectionBuilder { get; }

        public string Key { get; private set; }

        public string RedisHost { get; protected set; }

        /// <summary>
        /// TcpClient
        /// </summary>
        protected TcpClient TcpClient { get; set; }

        public DateTime LastUseTime { get; set; }

        private bool _isClose;
        public bool IsClose => _isClose ? _isClose : !TcpClient.Connected;

        public string ClientId { get; private set; }

        public int Port { get; protected set; }

        public int CurrentDBIndex { get; private set; }

        public int TargetDBIndex { get; private set; }

        public RedisClient(string key, TcpClient tcpClient, IConnectionBuilder connectionBuilder, string redisHost, int port, Action<IRedisClient> disposeCallback)
        {
            TcpClient = tcpClient;
            ConnectionBuilder = connectionBuilder;
            RedisHost = redisHost;
            Port = port;
            DisposeCallback = disposeCallback;
            Key = key;
            CurrentDBIndex = connectionBuilder.DBIndex;
            TargetDBIndex = connectionBuilder.DBIndex;
        }
        /// <summary>
        /// 实现销毁
        /// </summary>
        public void Dispose()
        {
            this.Dispose(true);
        }
        private bool _isDispose;

        ~RedisClient()
        {
            this.Dispose(false);
        }

        /// <summary>
        /// 销毁资源
        /// </summary>
        /// <param name="isDispose"></param>
        protected void Dispose(bool isDispose)
        {
            if (isDispose)
            {
                _isDispose = true;
                DisposeCallback?.Invoke(this);
            }
        }

        /// <summary>
        /// 关闭连接
        /// </summary>
        public void Close()
        {
            _isClose = true;
            TcpClient?.Dispose();
            TcpClient = null;
            // DisposeCallback = null;
            GC.SuppressFinalize(this);
        }

        public virtual async Task InitClientAsync()
        {
            await AuthAsync();
        }

        public virtual async Task<T> ExecuteAsync<T>(IRedisCommandArg commandArg)
        {
            var stream =
            await GetStreamAsync(commandArg.Cmd is RedisCommandConst.Auth or RedisCommandConst.Quit or RedisCommandConst.Hello);
            await MessageSendAsync(stream, commandArg);
            var result = await MessageTransport.ReceiveMessageAsync(stream);
            EventBus.WriteRedisAfter(commandArg.Cmd, commandArg.Args, result);
            if (result is T redisValue)
            {
                return redisValue;
            }

            return default(T);
        }

        public virtual async Task<RedisData> ExecuteSampleAsync(IRedisCommandArg commandArg)
        {
            var stream = await GetStreamAsync(commandArg.Cmd is RedisCommandConst.Auth or RedisCommandConst.Quit or RedisCommandConst.Hello);

            await MessageSendAsync(stream, commandArg);
            var result = await MessageTransport.ReceiveSimpleMessageAsync(stream);
            EventBus.WriteRedisAfter(commandArg.Cmd, commandArg.Args, result);
            return result;
        }

        public virtual async Task ExecuteNoResultAsync(IRedisCommandArg commandArg)
        {
            var stream =
          await GetStreamAsync(commandArg.Cmd is RedisCommandConst.Auth or RedisCommandConst.Quit or RedisCommandConst.Hello);
            await MessageSendAsync(stream, commandArg);
        }

        public async Task<object> ReadMessageAsync()
        {
            return await MessageTransport.ReceiveMessageAsync(await GetStreamAsync(true));
        }

        public async Task<RedisData> InvokeAsync(IRedisCommandArg commandArg)
        {
            return await ExecuteSampleAsync(commandArg);
        }

        public virtual async Task<bool> PingAsync()
        {
            return (await InvokeAsync(new RedisCommandArg(RedisCommandConst.Ping, default))) == RedisCommandConst.Pong;
        }

        public virtual async Task<bool> ChangeDb(int dbIndex)
        {
            if (dbIndex == CurrentDBIndex)
            {
                return true;
            }

            var res = (await InvokeAsync(new RedisCommandArg(RedisCommandConst.Select, new object[] { dbIndex }))) == RedisCommandConst.Ok;
            if (res)
            {
                CurrentDBIndex = dbIndex;
            }

            return res;
        }

        private async Task AuthAsync()
        {
            if (!IsAuth)
            {
                IsAuth = await AuthAsync(ConnectionBuilder.UserName, ConnectionBuilder.Password);
                if (!IsAuth)
                {
                    throw new InvalidOperationException("login fail");
                }

                await ChangeDb(ConnectionBuilder.DBIndex);
            }
        }

        public virtual async Task<bool> AuthAsync(string userName, string password)
        {
            if (ConnectionBuilder.RESP3)
            {
                return await ChangeProtocolAsync(RedisProtocolEnum.RESP3, userName, password);
            }

            //没有用户名，有密码
            if (string.IsNullOrWhiteSpace(userName) && !string.IsNullOrWhiteSpace(password))
            {
                return (await InvokeAsync(new RedisCommandArg(RedisCommandConst.Auth, new object[] { password }))) == RedisCommandConst.Ok;
            }

            if (!string.IsNullOrWhiteSpace(userName) && !string.IsNullOrWhiteSpace(password))
            {
                return (await InvokeAsync(new RedisCommandArg(RedisCommandConst.Auth, new object[] { userName, password }))) == RedisCommandConst.Ok;
            }

            return true;
        }

        private async Task<bool> ChangeProtocolAsync(RedisProtocolEnum protocolType, string userName = null, string password = null)
        {
            if (protocolType == RedisProtocolEnum.RESP3)
            {
                //没有密码，直接返回true
                if (string.IsNullOrWhiteSpace(userName) && string.IsNullOrWhiteSpace(password))
                {
                    return true;
                }
                object[] cmds = null;

                //没有用户名，有密码
                if (string.IsNullOrWhiteSpace(userName) && !string.IsNullOrWhiteSpace(password))
                {
                    cmds = new object[] { 3, RedisCommandConst.Auth, password };
                }

                //有密码，有用户名
                if (!string.IsNullOrWhiteSpace(userName) && !string.IsNullOrWhiteSpace(password))
                {
                    cmds = new object[] { 3, RedisCommandConst.Auth, userName, password };
                }

                var command = new RedisCommandArg(RedisCommandConst.Hello, cmds);

                var results = (await ExecuteAsync<IDictionary<string, object>>(command));

                var res = results != default && results.Count > 0;

                if (res && results.TryGetValue("id", out var clientId))
                {
                    ClientId = clientId.ToString();
                }
                return res;
            }
            else
            {
                return false;
            }
        }

        public virtual async Task<bool> QuitAsync()
        {
            return (await InvokeAsync(new RedisCommandArg(RedisCommandConst.Quit, default))) == RedisCommandConst.Ok;
        }

        public virtual async Task ReconnectAsync(CancellationToken cancellationToken = default)
        {
            //清除消息
            await ClearMessageAsync(cancellationToken);
            //重置db
            await ResetDbAsync(cancellationToken);
        }

        /// <summary>
        /// 清空所有的消息
        /// </summary>
        /// <param name="cancellationToken"></param>
        protected virtual async Task ClearMessageAsync(CancellationToken cancellationToken = default)
        {
            var tcpClient = TcpClient;
            if (tcpClient.Available <= 0)
            {
                return;
            }

            var stream = await GetStreamAsync(true);
            while (tcpClient.Available > 0)
            {
                using (var memoryOwner = MemoryPool<byte>.Shared.Rent(1024))
                {
                    _ = await stream.ReadAsync(memoryOwner.Memory, cancellationToken);
                }
            }
        }

        /// <summary>
        ///  重置db
        /// </summary>
        protected virtual async Task ResetDbAsync(CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();
            if (TargetDBIndex != CurrentDBIndex)
            {
                await ChangeDb(TargetDBIndex);
            }
        }

        public virtual async Task ReconnectSocketAsync(CancellationToken cancellationToken = default)
        {
            //设置连接状态为无效
            RedisClientConnectionStateManager.SetInValid(Key);
            //获取连接字符串
            var redisHostInfo = RedisClientConnectionStateManager.Get();
            Key = redisHostInfo.key;
            IsAuth = false;
            //释放连接
            TcpClient?.Dispose();
            TcpClient = null;
            //重新连接

            var client = new TcpClient();
            client.ReceiveTimeout = ConnectionBuilder.TimeOut;
            client.SendTimeout = ConnectionBuilder.TimeOut;

            await client.ConnectAsync(redisHostInfo.redisHost.Host, redisHostInfo.redisHost.Port);
            TcpClient = client;
            RedisHost = redisHostInfo.redisHost.Host;
            Port = redisHostInfo.redisHost.Port;
            CurrentDBIndex = ConnectionBuilder.DBIndex;

            //登录
            await AuthAsync();
        }

        private async Task<Stream> GetStreamAsync(bool needAuth = false)
        {
            if (needAuth)
            {
                await AuthAsync();
            }

            var tcp = TcpClient;
            if (tcp.Connected)
            {
                return tcp.GetStream();
            }

            await ReconnectSocketAsync();
            tcp = TcpClient;
            return tcp.GetStream();
        }

        /// <summary>
        /// 消息发送
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="command"></param>
        private async Task MessageSendAsync(Stream stream, IRedisCommandArg commandArg)
        {
            EventBus.WriteRedisBefore(commandArg.Cmd, commandArg.Args);
            await MessageTransport.SendAsync(stream, commandArg);
            //更新时间
            this.LastUseTime = DateTime.Now;
        }
    }
}
